3 Riuso delle classi in Java
Una delle caratteristiche principali di Java è la possibilità di riusare il codice. Esistono due modi di procedere:
Nel primo modo semplicemente si creano oggetti per la classe esistente all’interno della nuova classe. Questa modalità è chiamata composizione perché la nuova classe è composta di oggetti delle classi esistenti in modo tale che si riusa la funzionalità del codice.
Il secondo approccio si basa sul meccanismo di ereditarietà, uno dei principi fondamentali dell’Object Oriented.
3.1 Composizione
Mentre i tipi primitivi sono automaticamente inizializzati a 0, i riferimenti agli oggetti sono inizializzati a null.
Se si vuole un riferimento inizializzato, si può fare nei seguenti punti:
nel punto in cui gli oggetti sono definiti, quindi saranno sempre inizializzati immediatamente appena è richiamato il costruttore
nel costruttore della classe
giusto prima di quando si ha bisogno di usare l’oggetto. Questo approccio è spesso chiamato lazy initialization.
class Soap {
private String s;
// Inizializzazione nella costruzione
Soap() {
System.out.println("Soap()");
= new String("Constructed");
s }
public String toString() {
return s;
}
}
public class Bath {
private String s1 = new String("Happy"), s2 = "Happy", s3, s4;
;
Soap castilleint i;
float toy;
Bath() {
System.out.println("Inside Bath()");
= new String("Joy");
s3 = 47;
i = 3.14f;
toy = new Soap();
castille }
void print() {
// Lazy initialization
if(s4 == null) s4 = new String("Joy");
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
System.out.println("s3 = " + s3);
System.out.println("s4 = " + s4);
System.out.println("i = " + i);
System.out.println("toy = " + toy);
System.out.println("castille = " + castille);
}
public static void main(String[] args) {
= new Bath();
Bath b .print();
b}
}
/*
Output:
Inside Bath()
Soap()
s1 = Happy
s2 = Happy
s3 = Joy
s4 = Joy
i = 47
toy = 3.14
castille = Constructed
*/
3.2 Ereditarietà
L’ereditarietà è uno dei concetti fondamentali di Java (e di tutti i linguaggi object-oriented in generale).
Esso entra in gioco ogni qualvolta che si crea una classe perché ogni classe in Java eredita dalla classe standard di root Object (a meno di esplicitarne una in particolare).
Per indicare che una classe eredita i membri da un’altra classe Java mette a disposizione la parola chiave extends.
class Cleanser {
private String s = new String("Cleanser");
public void append(String a) { s += a; }
public void scrub() { append(" scrub()"); }
public void print() { System.out.println(s); }
public static void main(String[] args) {
= new Cleanser();
Cleanser x .scrub();
x.print();
x}
}
public class Detergent extends Cleanser {
public void scrub() {
append("Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Aggiunta di metodi
public void foam() {
append("foam()");
}
// Test della nuova classe
public static void main(String[] args) {
= new Detergent();
Detergent x .scrub();
x.foam();
x.print();
xSystem.out.println("Testing base class:");
.main(args);
Cleanser}
}
Detergent è derivata da Cleanser quindi prende automaticamente tutti i metodi della sua interfaccia anche se questi non sono definiti esplicitamente in Detergent.
Si può pensare alla ereditarietà come ad una sorta di riuso della interfaccia.
È possibile ridefinire i metodi ereditati dalla classe base. In tal caso se volessimo richiamare dalla sottoclasse il metodo scrub() definito nella classe base, dobbiamo utilizzare la parola chiave super che si riferisce alla superclasse da cui è stata derivata la classe corrente.
3.3 Inizializzazione della classe di base
Quando si crea un oggetto di una classe derivata, esso contiene al suo interno un sotto-oggetto della classe di base. Questo sotto-oggetto è identico a quello che si avrebbe se istanziassimo la classe base. Pertanto è estremamente importante che il sotto-oggetto sia inizializzato correttamente attraverso l’invocazione del costruttore della classe base. Le chiamate avvengono “dall’esterno verso l’interno”, ovvero a partire dalla radice della gerarchia di ereditarietà fino ad arrivare alla chiamata dell’ultimo costruttore. L’oggetto istanza della classe base viene creato prima di quello della sottoclasse.
Anche se non si crea un costruttore il compilatore ne costruirà uno di default (a zero argomenti) che invocherà il costruttore della classe base. Questa aggiunta non si ha se l’utente specifica un qualche costruttore. Se il costruttore della classe base ha degli argomenti in input allora bisogna esplicitare nella sua invocazione la lista di tali argomenti attraverso l’uso della parola chiave super.
class Game {
Game(int i) {
// super(); // prima di tutto viene chiamato il costruttore a 0 argomenti pubblico di Object, ma viene fatto in modo implicito
System.out.println("Game constructor");
}
}
class BoardGame extends Game {
BoardGame(int i) {
super(i);
System.out.println("BoardGame constructor");
}
}
public class Chess extends BoardGame {
Chess() {
super(69);
System.out.println("Chess constructor");
}
public static void main(String[] args) {
= new Chess();
Chess x }
}
/*
Output:
Game constructor
BoardGame constructor
Chess constructor
*/
Se non ci fosse la chiamata al costruttore della classe base in BoardGame(), il compilatore non troverebbe il costruttore Game(), segnalando un errore.
La chiamata al costruttore della classe base, deve essere la prima cosa da fare nel costruttore della classe derivata (altrimenti si verifica un errore di compilazione).
3.4 final applicato ai dati
In Java le costanti devono essere di tipo primitivo e sono espresse usando la parola chiave final. Inoltre deve essere assegnato loro un valore al momento della definizione.
Quando si usa final con i riferimenti agli oggetti cambiano leggermente le cose, infatti mentre con i tipi primitivi final rende il valore costante, con i riferimenti agli oggetti accade che rende i riferimenti costanti. Ciò significa che se si è inizializzato un riferimento ad un oggetto, esso non può più essere modificato sebbene l’oggetto stesso possa esserlo.
Java permette di rendere final gli argomenti di un metodo. Ciò significa che all’interno del metodo non si possono cambiare i riferimenti a cui gli argomenti puntano.
I metodi private sono implicitamente final perché non è possibile accedere ad un metodo private ne “sovrascriverlo”.
3.5 final applicato alle classi
Quando si dichiara una intera classe final, si specifica che non si desidera derivare delle classi da quella corrente.